#pragma once
#include<iostream>
#include<string>
#include<pthread.h>
#include<fstream>
#include<vector>
#include<sstream>
#include<sys/types.h>
#include<sys/socket.h>
#include<unordered_map>

#include"Socket.hpp"
#include"log.hpp"
const int defaultport=8888;
const string wwwroot="./wwwroot";//预设网站本地根目录
const string sep="\r\n";
const string homepage="index.html";

class HttpServer;//前向声明

class ThreadData
{
public:
    ThreadData(int fd,HttpServer*s) :sockfd(fd),svr(s)
    {}
public:
    int sockfd;
    HttpServer *svr;
};

class HttpRequest
{
public:
    void Deserialize(std::string req)//反序列化
    {
        while(true)
        {
            size_t pos=req.find(sep);//请求行之间通过\r\n分隔
            if(pos==string::npos) break;
            string temp=req.substr(0,pos);//左闭右开
            if(temp.empty()) break;
            req_header.push_back(temp);
            req.erase(0,pos+sep.size());//读取完情空当前报头，为下次做准备
        }
        text=req;
    }
    void Parse()//请求报头的解析
    {
        stringstream ss(req_header[0]);//作用1.数据类型转换 2.字符串拼接与分割
        ss>>method>>url>>http_version;//用 >> 操作符按空格分割提取信息
        file_path=wwwroot;
        if(url=="/"||url=="index.html")//默认首页请求
        {// ./wwwroot/index.html
            file_path+="/";
            file_path+=homepage;
        }
        else file_path+=url;// /a/b/c/d.html->./wwwroot/a/b/c/d.html，其他路径请求
        auto pos=file_path.rfind(".");
        if(pos==string::npos) suffix=".html";
        else suffix=file_path.substr(pos);
    }
    void DebugPrint()
    {
        for(auto &line:req_header)
        {//打印完整的原始请求
            cout<<"-----------------------------"<<endl;
            cout<<line<<"\r\n";
        }
        //打印解析后的结构化数据
        cout<<"method: "<<method<<endl;
        cout<<"url: "<<url<<endl;
        cout<<"http_version: "<<http_version<<endl;
        cout<<"file_path: "<<file_path<<endl;
        cout<<text<<endl;
    }
public:
    vector<string> req_header;//存取报头信息
    string text;//报头正文

    //解析之后的结果
    string method;
    string url;
    string http_version;
    string file_path;
    string suffix;//后缀，用于设置 HTTP 响应头的 Content-Type
};

class HttpServer
{
public:
    HttpServer(uint16_t port=defaultport) :port_(port)
    {//存储对应关系，也可以直接把所有对应关系放到文件中，然后读取文件方式插入
        content_type.insert({".html", "text/html"});
        content_type.insert({".png", "image/png"});
    }
    bool Start()
    {
        listensock_.Socket();
        listensock_.Bind(port_);
        listensock_.Listen();
        while(true)//不断等待客户链接，创建线程去响应
        {
            string clientip;
            uint16_t clientport;
            int sockfd=listensock_.Accept(&clientip,&clientport);
            if(sockfd<0) continue;
            lg(Info, "get a new connect, sockfd: %d",sockfd);
            pthread_t tid;
            ThreadData *td=new ThreadData(sockfd,this);//打包线程运行时需要的多个参数
            pthread_create(&tid,nullptr,ThreadRun,td);
        }
    }
    static string ReadHtmlContent(const string &htmlpath)//读取文本内容
    {
        ifstream in(htmlpath,ios::binary);//以二进制方式打开文件并读取，内容保真不修改任何字节
        if(!in.is_open()) return "";
        in.seekg(0,ios_base::end);  // 将文件指针移动到文件末尾
        auto len=in.tellg();        // 获取当前指针位置（即文件总字节数）
        in.seekg(0,ios_base::beg);  // 将指针移回文件开头，准备读取

        string content;
        content.resize(len);
        in.read((char*)content.c_str(),content.size());// 一次性读取所有内容
        in.close();
        return content;
        //以下读取方式存在文本模式局限，二进制文件不适用，比如图片或视频，可能因换行符误判导致内容缺失
        // std::string content;
        // std::string line;
        // while(std::getline(in, line)) { content += line; }
    }
    string SuffixToDesc(string &suffix)
    {
        auto iter = content_type.find(suffix);
        if(iter == content_type.end()) return content_type[".html"];
        else return content_type[suffix];
    }
    void HandlerHttp(int sockfd)//响应处理
    {
        char buffer[10240];
        ssize_t n=recv(sockfd,buffer,sizeof(buffer)-1,0);//bug??
        if(n>0)
        {
            buffer[n]=0;//假设读到的就是一个完整独立的请求
            cout<<buffer<<endl;
            HttpRequest req;
            req.Deserialize(buffer);
            req.Parse();
            //req.DebugPrint();
            //返回响应的过程
            //采取硬编码的方式返回网页
            //string text="<html><body><h3>hello world</h3></body></html>";
            string text;//采取读取文本获取网页信息
            bool ok=true;
            text=ReadHtmlContent(req.file_path);
            if(text.empty())
            {
                ok=false;
                 // 构建错误页面的路径（假设错误页面err.html放在wwwroot根目录下）
                string err_html=wwwroot;
                err_html+="/";
                err_html+="err.html";
                // 读取错误页面内容，覆盖text
                text=ReadHtmlContent(err_html);
            }

            string response_line;// 响应的状态行
            if(ok) response_line="HTTP/1.0 200 OK\r\n";
            else response_line="HTTP/1.0 404 NOT Found\r\n";
            //response_line = "HTTP/1.0 302 Found\r\n";//重定向信息，测试
            string response_header="Content-Length: ";//添加报头信息
            response_header+=to_string(text.size());
            response_header+="\r\n";
            response_header+="Content-Type: ";
            response_header+=SuffixToDesc(req.suffix);
            response_header+="\r\n";
            response_header+="Set-Cookie: name=ywb";
            response_header+="\r\n";
            response_header+="Set-Cookie: hieght=185";
            response_header+="\r\n";
            response_header+="Set-Cookie: weight=150";

            response_header+="\r\n";
            //response_header += "Location: https://www.qq.com\r\n";//添加跳转地址信息
            string blank_line = "\r\n"; // \n也可以,添加空行

            string response=response_line;
            response+=response_header;
            response+=blank_line;
            response+=text;
            send(sockfd,response.c_str(),response.size(),0);
        }
        close(sockfd);
    }
    static void *ThreadRun(void *args)
    {
        pthread_detach(pthread_self());//分离线程，让父进程不用等待
        ThreadData *td=static_cast<ThreadData *>(args);
        td->svr->HandlerHttp(td->sockfd);
        delete td;
        return nullptr;
    }
    ~HttpServer()
    {}
public:
    Sock listensock_;
    uint16_t port_;
    unordered_map<string,string> content_type;//存储文件后缀名与HTTP响应头Content-Type的对应关系
};